import math
from dataclasses import dataclass, field
from concurrent.futures import ThreadPoolExecutor
import numpy as np
from scipy.ndimage import zoom
import time
from components.CoordTransfer import CoordTransfer
from components.dicom_tag_info import DicomTagInfo
from share import machine_config
from type import EnumViewType, EnumMIPMode
from typing import Tuple, Any
from config import Scale_Percent_Setting
import os


def np_to_hu_np(pixel_array, rescale_slope, rescale_intercept):
    np_pixel = np.asarray(pixel_array, dtype=np.float32)
    hu_pixel_array = np_pixel * rescale_slope + rescale_intercept
    return hu_pixel_array


def vector3_list_to_hu_list(phase_index_to_ds_arr_dict, rescale_slope, rescale_intercept):
    hu_phase_list = []
    phase_list = []

    for phase_index in phase_index_to_ds_arr_dict:
        hu_dicom_list = []
        dicom_list = []
        for ds in phase_index_to_ds_arr_dict[phase_index]:
            dicom_pixel = ds.pixel_array
            np_dicom_pixel = np.asarray(dicom_pixel, dtype=np.float32)
            hu_dicom_pixel = np_dicom_pixel * rescale_slope + rescale_intercept

            hu_dicom_list.append(hu_dicom_pixel)
            dicom_list.append(dicom_pixel)

        hu_phase_list.append(hu_dicom_list)
        phase_list.append(dicom_list)
    return hu_phase_list, phase_list


def vector2_list_to_hu_list(pixel_array, rescale_slope, rescale_intercept):
    dicom_list = []
    for dicom_pixel in pixel_array:
        np_dicom_pixel = np.asarray(dicom_pixel, dtype=np.float32)
        dicom_pixel = np_dicom_pixel * rescale_slope + rescale_intercept
        dicom_list.append(dicom_pixel)
    return dicom_list


def volume_to_hu_volume(volume, rescale_slope, rescale_intercept):
    return volume * rescale_slope + rescale_intercept


@dataclass
class SlicePosition:
    # 没有归一化的切片中心坐标
    origin_position = (0, 0, 0)  # voxel坐标下的位置。 0 ~ len - 1
    origin_shape = (0, 0, 0)
    norm_shape = (0, 0, 0)
    # 归一化后的mpr中心小球的位置。也就是切片的点。 默认应该在中心位置。
    physical_position = (0, 0, 0)
    slice_in_screen_position = {  # 切片点在屏幕上的坐标。
        EnumViewType.Ax.value: (0, 0),
        EnumViewType.Sag.value: (0, 0),
        EnumViewType.Cor.value: (0, 0),
    }

    def set_origin_position(self, position):
        self.origin_position = tuple(round(position[i]) for i in range(3))

    def set_physical_position(self, position):
        self.physical_position = position

    def set_origin_shape(self, shape, spacing):
        self.origin_shape = shape
        self.norm_shape = tuple(round(shape[i] * spacing[i]) for i in range(3))

    def get_shape_by_view_type(self, view_type):
        if view_type == EnumViewType.Ax.value:
            return self.origin_shape[1], self.origin_shape[2]
        elif view_type == EnumViewType.Sag.value:
            return self.origin_shape[0], self.origin_shape[2]
        elif view_type == EnumViewType.Cor.value:
            return self.origin_shape[0], self.origin_shape[2]

    def get_norm_shape_by_view_type(self, view_type):
        instances, height, width = self.norm_shape
        if view_type == EnumViewType.Ax.value:
            return height, width
        elif view_type == EnumViewType.Sag.value:
            return instances, height
        elif view_type == EnumViewType.Cor.value:
            return instances, width

    def set_slice_in_screen_position(self, view_type, position):
        self.slice_in_screen_position[view_type] = position


@dataclass
class PhaseData:
    def __init__(self, current, total):
        self.current = current
        self.total = total

    def set_current(self, index):
        self.current = index

    def set_total(self, total):
        self.total = total

    def set_next(self):
        if self.current >= self.total - 1:
            self.current = 0
        else:
            self.current = self.current + 1


def batch_resize_volume(norm_mpr_volume, mpr_resize_factor):
    gpu_open = machine_config['gpu']
    if gpu_open:
        return gpu_resize_volume(norm_mpr_volume, mpr_resize_factor)
    else:
        return cpu_resize_volume(norm_mpr_volume, mpr_resize_factor)


def gpu_resize_volume(volume, mpr_resize_factor):
    import torch
    import torch.nn.functional as F
    input_tensor = torch.from_numpy(volume).float()

    # 如果输入是4D的(batch, depth, height, width)
    # 需要添加channel维度使其变为5D(batch, channel, depth, height, width)
    if input_tensor.dim() == 4:
        input_tensor = input_tensor.unsqueeze(1)

    # 进行插值
    output_tensor = F.interpolate(
        input_tensor,
        scale_factor=mpr_resize_factor,  # 不包括batch维度的缩放
        mode='trilinear',
        align_corners=False
    )

    # 将结果转回CPU的numpy数组
    # 如果之前添加了channel维度，这里要去掉
    result = output_tensor.squeeze(1).cpu().numpy()
    return result


def cpu_resize_volume(norm_mpr_volume, mpr_resize_factor):
    phase_count, height, _, _ = norm_mpr_volume.shape
    cpu_count = machine_config['cpu_count']
    print('cpu数量是：', cpu_count)
    if phase_count == 1:
        # 将单个volume分成10份来并行处理
        chunk_size = height // cpu_count

        def process_chunk(chunk_data):
            return zoom(chunk_data,
                        mpr_resize_factor,
                        order=3)

        # 准备数据块
        chunks = []
        for i in range(cpu_count):
            start_idx = i * chunk_size
            end_idx = start_idx + chunk_size if i < cpu_count - 1 else height
            chunks.append(norm_mpr_volume[0, start_idx:end_idx, :, :])

        # 并行处理各个块
        with ThreadPoolExecutor(max_workers=cpu_count) as executor:
            resized_chunks = list(executor.map(process_chunk, chunks))

        # 在高度维度上合并结果
        # np.concatenate 是在已有的轴上合并数组
        resized_volume = np.concatenate(resized_chunks, axis=0)
        return resized_volume[np.newaxis, ...]  # 添加phase维度

    def process_single_volume(args):
        volume, resize_factor = args
        return zoom(volume, resize_factor, order=3)

    args_list = [(vol, mpr_resize_factor) for vol in norm_mpr_volume]

    with ThreadPoolExecutor(max_workers=cpu_count) as executor:  # 可以根据CPU核心数调整max_workers
        resized_volumes = list(executor.map(process_single_volume, args_list))

    # np.stack 会创建一个新的轴来组合数组
    return np.stack(resized_volumes, axis=0)


@dataclass
class MIPData:
    use_mip = False
    mip_mode: EnumMIPMode = EnumMIPMode.mip.value
    mip_val = {
        EnumViewType.Ax.value: 0,  # 表示的是mip模式下。需要在ax轴上计算的物理距离(mm)
        EnumViewType.Sag.value: 0,
        EnumViewType.Cor.value: 0,
    }
    mip_range_in_norm_voxel = {
        EnumViewType.Ax.value: (0, 0),  # mip模式下在每个轴上累计的norm_voxel坐标范围。
        EnumViewType.Sag.value: (0, 0),
        EnumViewType.Cor.value: (0, 0),
    }

    def set_all(self, use_mip, mip_mode, mip_val):
        self.use_mip = use_mip
        self.mip_val = mip_val
        self.mip_mode = mip_mode

    def set_mip_val(self, min_val):
        self.mip_val = min_val

    def set_mip_range_in_norm_voxel(self, val):
        self.mip_range_in_norm_voxel = val


@dataclass
class ViewportTransform:
    scale: float = 1.0  # 缩放比例
    rotation: float = 0.0  # 旋转角度(弧度)
    flip_horizontal: bool = False
    flip_vertical: bool = False
    scale_center = (0, 0)


@dataclass
class ViewAttr:
    origin_index = 0  # 当前在原始图像中的index，从0 开始
    origin_total = 0  # 原始图像中的总数， 总数。
    dicom_size = (0, 0)  # h,w。 dicom像素的大小， 以 320 * 512 * 512的图像为例。是512 * 512
    scaled_size = (0, 0)  # 初始状态下。由normed_size 缩放来满足png_size 的大小后 的大小。
    png_size = (0, 0)  # h,w 前端显示的图片大小
    padding = 0  # 前端显示的图片中为了dicom影响不是靠边显示，初始化设置的padding值。
    window = (0, 0)  # window center  window_width
    origin_transform_matrix = np.identity(3)  # 初始的仿射变换矩阵。主要用于重置。
    transform_matrix = np.identity(3)  # 变换矩阵
    viewport_transform: ViewportTransform = field(default_factory=ViewportTransform)  # 用来记录当前视图所有的变换信息
    mouse_position = (0, 0)  # 最近鼠标在该视图中显示的位置


class VolumeInfo:
    def __init__(self, dicom_tag_info, coord_transfer):
        self.mip_data = MIPData()
        self.origin_volume_list = []
        self.origin_volume = None

        self.origin_hu_volume_list = []
        self.origin_hu_volume = None

        self.mpr_volume = None
        self.correct_mpr_volume = None
        self.spacing_info = None
        self.dicom_tag_info: DicomTagInfo = dicom_tag_info
        self.view_info = {
            EnumViewType.Stack.value: ViewAttr(),
            EnumViewType.Sag.value: ViewAttr(),
            EnumViewType.Cor.value: ViewAttr(),
            EnumViewType.Ax.value: ViewAttr(),
        }
        self.slice_position: SlicePosition = SlicePosition()
        self.coord_transfer: CoordTransfer = coord_transfer
        self.phase_data: PhaseData = PhaseData(0, 0)

    def set_canvas_size(self, view_type, size):
        self.view_info[view_type].png_size = size

    def init_origin_volume_by_exist_files(self, phase_index_to_ds_arr_dict):
        rescale_slope = self.dicom_tag_info.tags['RescaleSlope']
        rescale_intercept = self.dicom_tag_info.tags['RescaleIntercept']
        hu_pixel_array, pixel_arrays = vector3_list_to_hu_list(phase_index_to_ds_arr_dict, rescale_slope,
                                                               rescale_intercept)
        self.origin_volume_list = pixel_arrays
        self.origin_hu_volume_list = hu_pixel_array
        self.update_index_by_volume()
        self.check_and_set_phase_total()

    def init_origin_volume_by_coming_file(self, pixel_array, phase_index):
        rescale_slope = self.dicom_tag_info.tags['RescaleSlope']
        rescale_intercept = self.dicom_tag_info.tags['RescaleIntercept']
        hu_pixel_array = vector2_list_to_hu_list(pixel_array, rescale_slope, rescale_intercept)
        self.check_need_extend_arr(phase_index)
        self.origin_volume_list[phase_index] = pixel_array
        self.origin_hu_volume_list[phase_index] = hu_pixel_array
        self.phase_data.set_current(phase_index)
        print('设置当前的相位是：', phase_index)
        self.update_index_by_volume()
        self.check_and_set_phase_total()

    def check_need_extend_arr(self, phase_index):
        while len(self.origin_volume_list) <= phase_index:
            self.origin_volume_list.append([])
            self.origin_hu_volume_list.append([])

    def check_and_set_phase_total(self):
        max_phase = 0
        for index in range(len(self.origin_volume_list)):
            if len(self.origin_volume_list[index]) > 0:
                max_phase = index
        self.phase_data.set_total(max_phase)

    def add_a_slice(self, pixel_array, phase_index=0):

        rescale_slope = self.dicom_tag_info.tags['RescaleSlope']
        rescale_intercept = self.dicom_tag_info.tags['RescaleIntercept']

        start_time = time.time()

        hu_pixel_array = np_to_hu_np(pixel_array, rescale_slope, rescale_intercept)
        end_time = time.time()
        # print(f'load_a_dicom,运行时间：,{end_time - start_time}')
        self.phase_data.set_total(len(self.origin_volume_list))
        self.origin_volume_list[phase_index].append(pixel_array)
        self.origin_hu_volume_list[phase_index].append(hu_pixel_array)
        self.phase_data.set_current(phase_index)
        print(f'向phase: {phase_index} 添加了一个dicom')
        self.update_index_by_volume()
        self.check_and_set_phase_total()

    def get_hu_volume(self, volume):
        rescale_slope = self.dicom_tag_info.tags['RescaleSlope']
        rescale_intercept = self.dicom_tag_info.tags['RescaleIntercept']
        hu_volume = volume_to_hu_volume(volume, rescale_slope, rescale_intercept)
        return hu_volume

    def update_index_by_volume(self):

        self.view_info[EnumViewType.Stack.value].origin_total = len(self.origin_volume_list[self.phase_data.current])
        self.view_info[EnumViewType.Stack.value].origin_index = len(
            self.origin_volume_list[self.phase_data.current]) - 1

    def origin_list_to_np_arr(self):
        self.origin_volume = np.stack(self.origin_volume_list, axis=0)
        self.origin_hu_volume = np.stack(self.origin_hu_volume_list, axis=0)

    def init_norm_volume(self):

        self.origin_list_to_np_arr()
        orientation_matrix = self._get_orientation_matrix()
        mpr_volume = self._reorient_volume(orientation_matrix, self.origin_volume)

        mpr_hu_volume = self.get_hu_volume(mpr_volume)
        self.mpr_volume = mpr_hu_volume

        self.correct_mpr_volume = self.correct_volume(mpr_hu_volume)

        self.init_mpr_slice_position()

    def correct_volume(self, norm_mpr_volume):
        spacing = self.spacing_info
        mpr_target_spacing = (1.0, 1.0, 1.0)
        mpr_resize_factor = [old / new for old, new in zip(spacing, mpr_target_spacing)]
        resized_mpr_volume = batch_resize_volume(norm_mpr_volume, mpr_resize_factor)
        return resized_mpr_volume

    def init_mpr_slice_position(self):
        origin_shape = tuple(int(x) for x in self.origin_hu_volume[self.phase_data.current].shape)
        origin_slice_position = tuple(int(x / 2) for x in self.origin_hu_volume[self.phase_data.current].shape)
        self.slice_position.set_origin_shape(origin_shape, self.spacing_info)
        self.update_origin_slice_position(origin_slice_position)

    def update_physical_slice_position(self, physical_position):
        self.slice_position.set_physical_position(physical_position)

    def update_origin_slice_position(self, position):
        rounded_voxel_position = tuple(round(position[i]) for i in range(3))
        self.slice_position.set_origin_position(rounded_voxel_position)
        physical_point = self.voxel_to_physical_coordinate(rounded_voxel_position)
        self.update_physical_slice_position(physical_point)
        self.update_mip_range()

    def init_spacing_info(self):
        if self.spacing_info is None:
            pixel_spacing = self.dicom_tag_info.tags['pixel_spacing']
            thickness = self.dicom_tag_info.tags['thickness']
            self.spacing_info = (thickness, pixel_spacing[0], pixel_spacing[1])

    def _get_orientation_matrix(self) -> np.ndarray:
        """
        根据DICOM的ImageOrientationPatient创建方向矩阵
        """
        # 提取行方向向量和列方向向量
        row_vector = np.array(self.dicom_tag_info.tags['image_orientation'][:3])
        col_vector = np.array(self.dicom_tag_info.tags['image_orientation'][3:])

        # 计算切片方向（行向量和列向量的叉积）
        slice_vector = np.cross(row_vector, col_vector)

        # 创建方向矩阵
        orientation_matrix = np.vstack([row_vector, col_vector, slice_vector])
        return orientation_matrix

    def _reorient_volume(self, orientation_matrix, volume) -> np.ndarray:
        """
        重定向体数据到标准解剖方向(LPS: Left, Posterior, Superior)
        """
        patient_position = self.dicom_tag_info.tags['patient_position']
        # 计算仿射变换参数
        cosines = orientation_matrix

        # 根据患者体位进行基本调整
        if patient_position == 'HFS':  # Head First-Supine
            pass
        elif patient_position == 'FFS':  # Feet First-Supine
            volume = np.flip(volume, axis=1)
        elif patient_position == 'HFP':  # Head First-Prone
            volume = np.flip(volume, axis=3)
        elif patient_position == 'FFP':  # Feet First-Prone
            volume = np.flip(volume, axis=(1, 3))

        # 判断是否需要轴交换或翻转
        # 检查每个方向向量与标准方向的夹角
        primary_directions = np.abs(cosines)

        # 找出主方向
        axis_order = np.array([np.argmax(primary_directions[i]) for i in range(3)])
        flips = np.array([1 if cosines[i, axis_order[i]] > 0 else -1 for i in range(3)])

        axis_order_4d = np.array([x + 1 for x in axis_order])
        # 应用轴交换
        volume = np.transpose(volume, (0,) + tuple(axis_order_4d))

        # 应用翻转
        for axis, flip in enumerate(flips):
            if flip < 0:
                volume = np.flip(volume, axis=axis + 1)
        return volume

    def get_current_slice_index(self, view_type):
        if view_type == EnumViewType.Stack.value:
            return self.view_info[EnumViewType.Stack.value].origin_index
        elif view_type == EnumViewType.Sag.value:
            return self.slice_position.origin_position[1]
        elif view_type == EnumViewType.Cor.value:
            return self.slice_position.origin_position[2]
        elif view_type == EnumViewType.Ax.value:
            return self.slice_position.origin_position[0]

    def get_total_slice(self, view_type):
        if view_type == EnumViewType.Stack.value:
            return self.view_info[EnumViewType.Stack.value].origin_total
        elif view_type == EnumViewType.Sag.value:
            return self.slice_position.origin_shape[1]
        elif view_type == EnumViewType.Cor.value:
            return self.slice_position.origin_shape[2]
        elif view_type == EnumViewType.Ax.value:
            return self.slice_position.origin_shape[0]

    def init_mpr_view_current_index(self):
        for view_type in [EnumViewType.Ax.value, EnumViewType.Sag.value,
                          EnumViewType.Cor.value]:
            origin_total = self.get_total_slice(view_type)
            if view_type == EnumViewType.Stack.value:
                self.set_origin_current_index(0, EnumViewType.Stack.value)
            else:
                self.set_origin_current_index(round(origin_total // 2), view_type)

    def init_stack_view_current_index(self):
        origin_total = self.get_total_slice(EnumViewType.Stack.value)
        self.set_origin_current_index(origin_total - 1, EnumViewType.Stack.value)

    def get_current_dicom_data(self, view_type):
        index = self.get_current_slice_index(view_type)
        if view_type == EnumViewType.Stack.value:
            return self.origin_hu_volume_list[self.phase_data.current][index]
        correct_volume = self.correct_mpr_volume[self.phase_data.current, :, :, :]
        match view_type:
            case EnumViewType.Sag.value:
                mip_range = self.mip_data.mip_range_in_norm_voxel[EnumViewType.Sag.value]
                mip_range_scope = abs(mip_range[0] - mip_range[1])
                if self.mip_data.use_mip is False or mip_range_scope <= 1:
                    target = round(index * self.spacing_info[1])
                    return correct_volume[:, :, target]
                else:
                    volume_section = correct_volume[:, :, math.floor(mip_range[0]):math.floor(mip_range[1])]
                    return self.apply_mip_mode(volume_section, 2)
            case EnumViewType.Cor.value:
                mip_range = self.mip_data.mip_range_in_norm_voxel[EnumViewType.Cor.value]
                mip_range_scope = abs(mip_range[0] - mip_range[1])
                if self.mip_data.use_mip is False or mip_range_scope <= 1:
                    target = round(index * self.spacing_info[2])
                    return correct_volume[:, target, :]
                else:
                    volume_section = correct_volume[:, math.floor(mip_range[0]):math.floor(mip_range[1]), :]
                    return self.apply_mip_mode(volume_section, 1)
            case EnumViewType.Ax.value:
                mip_range = self.mip_data.mip_range_in_norm_voxel[EnumViewType.Ax.value]
                mip_range_scope = abs(mip_range[0] - mip_range[1])
                if self.mip_data.use_mip is False or mip_range_scope <= 1:
                    # todo 这里如果index * self.spacing_info[0]
                    #  得到的是一个小数。该取上一层还是下一层还是平均值？
                    floor_target = math.floor(index * self.spacing_info[0])
                    ceil_target = math.ceil(index * self.spacing_info[0])
                    if floor_target != ceil_target:
                        volume_section = correct_volume[floor_target: ceil_target, :, :]
                        return np.mean(volume_section, 0)
                    else:
                        return correct_volume[floor_target, :, :]
                else:
                    volume_section = correct_volume[math.floor(mip_range[0]): math.floor(mip_range[1]), :, :]
                    return self.apply_mip_mode(volume_section, 0)

    def apply_mip_mode(self, volume_section, axis):
        mip_mode = self.mip_data.mip_mode
        if mip_mode == EnumMIPMode.mip.value:
            return np.max(volume_section, axis)

        elif mip_mode == EnumMIPMode.min_ip.value:
            return np.min(volume_section, axis)

        elif mip_mode == EnumMIPMode.mean.value:
            return np.mean(volume_section, axis)

        elif mip_mode == EnumMIPMode.sum.value:
            return np.sum(volume_section, axis)
        else:
            raise ValueError("mip mode set error")

    def get_origin_current_total(self, view_type):
        current_origin_index = self.get_current_slice_index(view_type)
        return current_origin_index, self._get_total_index_by_type(view_type)

    def _get_total_index_by_type(self, view_type):
        if view_type == EnumViewType.Stack.value:
            return self.view_info[EnumViewType.Stack.value].origin_total
        _, instances, height, width = self.mpr_volume.shape
        total_index = 0
        match view_type:
            case EnumViewType.Ax.value:
                total_index = instances
            case EnumViewType.Sag.value:
                total_index = width
            case EnumViewType.Cor.value:
                total_index = height
        return total_index

    def set_origin_current_index(self, index, view_type):
        origin_slice_position = self.slice_position.origin_position
        match view_type:
            case EnumViewType.Ax.value:
                self.update_origin_slice_position((index, origin_slice_position[1], origin_slice_position[2]))
            case EnumViewType.Sag.value:
                self.update_origin_slice_position((origin_slice_position[0], index, origin_slice_position[2]))
            case EnumViewType.Cor.value:
                self.update_origin_slice_position((origin_slice_position[0], origin_slice_position[1], index))
            case EnumViewType.Stack.value:
                self.view_info[EnumViewType.Stack.value].origin_index = index

    def get_mpr_all_view_index(self):
        ans_dict = {}
        for each in [EnumViewType.Sag.value, EnumViewType.Cor.value, EnumViewType.Ax.value]:
            ans_dict[each] = {
                "origin_index": self.get_current_slice_index(each),
                "origin_total": self.get_total_slice(each)
            }
        return ans_dict

    # 用来处理前端直接移动cross的情况。
    def update_slice_position(self, view_type, x, y):
        png_x = x
        png_y = y
        current_origin_slice_position = self.slice_position.origin_position  # 320 * 512 * 512
        origin_dicom_position = self.screen_position_to_clipped_voxel_position(view_type, png_x, png_y)
        origin_x, origin_y = origin_dicom_position
        new_origin_slice_position = (0, 0, 0)
        match view_type:
            case EnumViewType.Ax.value:
                new_origin_slice_position = (current_origin_slice_position[0], origin_y, origin_x)
            case EnumViewType.Sag.value:
                new_origin_slice_position = (origin_x, current_origin_slice_position[1], origin_y)
            case EnumViewType.Cor.value:
                new_origin_slice_position = (origin_x, origin_y, current_origin_slice_position[2])
        self.update_origin_slice_position(new_origin_slice_position)

    def get_spacing(self, view_type: EnumViewType):
        match view_type:
            case EnumViewType.Ax.value:
                return self.spacing_info[1], self.spacing_info[2]
            case EnumViewType.Sag.value:
                return self.spacing_info[1], self.spacing_info[0]
            case EnumViewType.Cor.value:
                return self.spacing_info[1], self.spacing_info[0]
            case EnumViewType.Stack.value:
                return self.spacing_info[1], self.spacing_info[2]

    def set_next_slice(self, view_type):
        origin_index = self.get_current_slice_index(view_type)
        origin_total = self.get_total_slice(view_type)

        next_origin_index = origin_index + 1

        if next_origin_index >= origin_total:
            self.set_origin_current_index(0, view_type)
            return False
        else:
            self.set_origin_current_index(next_origin_index, view_type)
            return True

    def set_prev_slice(self, view_type):
        origin_index = self.get_current_slice_index(view_type)
        next_origin_index = origin_index - 1
        if next_origin_index <= -1:
            return False
        else:
            self.set_origin_current_index(next_origin_index, view_type)
            return True

    def set_slice_index(self, view_type, to_index):
        self.set_origin_current_index(to_index, view_type)

    def init_window(self, view_type):
        size_info = self.view_info[view_type]
        size_info.window = (self.dicom_tag_info.tags['window_center'], self.dicom_tag_info.tags['window_width'])

    def get_norm_dicom_size(self, view_type):
        if view_type == EnumViewType.Stack.value:
            height, width = self.origin_volume_list[self.phase_data.current][0].shape
            return height, width
        else:
            return self.slice_position.get_norm_shape_by_view_type(view_type)

    def init_transform(self, viewer_arr):
        for each_view in viewer_arr:
            size_info = self.view_info[each_view]
            viewport_transform = self.view_info[each_view].viewport_transform
            self.init_window(each_view)
            full_display_size = size_info.png_size
            padding = size_info.padding
            display_size = (full_display_size[0] - padding * 2, full_display_size[1] - padding * 2)
            dicom_size = self.get_norm_dicom_size(each_view)
            scale_h = display_size[0] / dicom_size[0]
            scale_w = display_size[1] / dicom_size[1]

            scale = min(scale_w, scale_h)
            viewport_transform.scale = min(scale_w, scale_h)

            # 计算缩放后的图像大小
            size_info.scaled_size = (
                int(dicom_size[0] * scale),
                int(dicom_size[1] * scale)
            )

            pan_x = (full_display_size[1] - size_info.scaled_size[1]) // 2
            pan_y = (full_display_size[0] - size_info.scaled_size[0]) // 2

            origin_transform_matrix = np.array([
                [scale, 0, pan_y],
                [0, scale, pan_x],
                [0, 0, 1]
            ])
            self.set_origin_transform_matrix(each_view, origin_transform_matrix)
            self.init_transform_matrix(view_type=each_view)

    def init_transform_matrix(self, view_type):
        origin_transform_matrix = self.view_info[view_type].origin_transform_matrix
        self.view_info[view_type].transform_matrix = origin_transform_matrix

    def set_origin_transform_matrix(self, view_type, origin_transform_matrix):
        self.view_info[view_type].origin_transform_matrix = origin_transform_matrix

    def update_transform_matrix(self, view_type, new_matrix):
        current_transform_matrix = self.view_info[view_type].transform_matrix
        self.view_info[view_type].transform_matrix = new_matrix @ current_transform_matrix

    def screen_coord_to_origin_voxel(self, view_type, display_x, display_y):
        transform_matrix = self.view_info[view_type].transform_matrix
        origin_voxel_point = self.coord_transfer.screen_to_origin_voxel(view_type, transform_matrix,
                                                                        (display_x, display_y))
        return origin_voxel_point

    def origin_voxel_coord_to_screen_coord(self, view_type, point):
        matrix = self.view_info[view_type].transform_matrix
        screen_point = self.coord_transfer.origin_voxel_to_screen(view_type, point, transform_matrix=matrix)
        return screen_point

    def get_cross_center(self, view_type):
        return self.get_current_cross_point(view_type)

    def get_current_cross_point(self, view_type):
        vector3_position = self.slice_position.origin_position
        position = (0, 0)
        match view_type:
            case EnumViewType.Ax.value:
                position = (vector3_position[1], vector3_position[2])
            case EnumViewType.Sag.value:
                position = (vector3_position[2], vector3_position[0])
            case EnumViewType.Cor.value:
                position = (vector3_position[1], vector3_position[0])
        screen_position = self.origin_voxel_coord_to_screen_coord(view_type, position)
        self.slice_position.set_slice_in_screen_position(view_type, screen_position)
        return screen_position

    def screen_position_to_clipped_voxel_position(self, view_type, png_x, png_y):
        self.view_info[view_type].mouse_position = (png_x, png_y)
        origin_voxel_position = self.screen_coord_to_origin_voxel(view_type, png_x, png_y)
        clip_min = (0, 0, 0)
        clip_max = self.slice_position.get_shape_by_view_type(view_type)
        clip_max = tuple(x - 1 for x in clip_max)
        clipped_origin_voxel_position = tuple(
            max(min(x, y_max), y_min) for x, y_min, y_max in zip(origin_voxel_position, clip_min, clip_max)
        )
        return clipped_origin_voxel_position

    def set_transfer_info_window(self, view_type, delta_x, delta_y):
        window = self.view_info[view_type].window
        target_window = (window[0] + delta_y, window[1] + delta_x)
        self.view_info[view_type].window = target_window

    def transform_viewport_pan(self, view_type, delta_x, delta_y):
        if delta_x != 0 and delta_y != 0:
            matrix = np.array([
                [1, 0, delta_y],
                [0, 1, delta_x],
                [0, 0, 1]
            ])
            self.update_transform_matrix(view_type, matrix)

    def transform_viewport_zoom(self, view_type, new_zoom_factor):
        viewport_transform = self.view_info[view_type].viewport_transform
        png_size = self.view_info[view_type].png_size
        center_y = round(png_size[0] // 2)
        center_x = round(png_size[1] // 2)

        new_zoom_percent = viewport_transform.scale + new_zoom_factor
        clipped_zoom_percent = np.clip(new_zoom_percent, Scale_Percent_Setting[0], Scale_Percent_Setting[1])

        percent = clipped_zoom_percent / viewport_transform.scale
        translate_to_origin = np.array([
            [1, 0, -center_x],
            [0, 1, -center_y],
            [0, 0, 1]
        ], dtype=np.float32)

        zoom_matrix = np.array([
            [percent, 0, 0],
            [0, percent, 0],
            [0, 0, 1]
        ], dtype=np.float32)

        translate_back = np.array([
            [1, 0, center_x],
            [0, 1, center_y],
            [0, 0, 1]
        ], dtype=np.float32)
        viewport_transform.scale = clipped_zoom_percent
        transform_matrix = translate_back @ zoom_matrix @ translate_to_origin
        self.update_transform_matrix(view_type, transform_matrix)

    def transform_viewport_rotate(self, view_type, clockwise: bool, angle):
        image_height, image_width = self.view_info[view_type].png_size
        viewport_transform = self.view_info[view_type].viewport_transform
        sin_val = math.sin(angle / 180 * math.pi)
        cos_val = math.cos(angle / 180 * math.pi)

        delta_x = image_width // 2 - image_width // 2 * cos_val + image_height // 2 * sin_val
        delta_y = image_height // 2 - image_height // 2 * sin_val - image_height // 2 * cos_val

        if clockwise:
            rotate_matrix = np.array([
                [cos_val, sin_val, delta_y],
                [-sin_val, cos_val, delta_x],
                [0, 0, 1]
            ], dtype=np.float32)
            viewport_transform.rotation = viewport_transform.rotation - angle
        else:
            rotate_matrix = np.array([
                [cos_val, -sin_val, delta_x],
                [sin_val, cos_val, delta_y],
                [0, 0, 1]
            ], dtype=np.float32)
            viewport_transform.rotation = viewport_transform.rotation + angle
        self.update_transform_matrix(view_type, rotate_matrix)

    def transform_viewport_flip(self, view_type, new_hor_flip, new_ver_flip):
        viewport_transform = self.view_info[view_type].viewport_transform
        hor_flag = False
        ver_flag = False
        if viewport_transform.flip_horizontal != new_hor_flip:
            hor_flag = True
        if viewport_transform.flip_vertical != new_ver_flip:
            ver_flag = True
        viewport_transform.flip_horizontal = new_hor_flip
        viewport_transform.flip_vertical = new_ver_flip

        image_height, image_width = self.view_info[view_type].png_size
        flip = np.identity(3)
        if hor_flag:
            flip = np.array([
                [-1, 0, image_height],
                [0, 1, 0],
                [0, 0, 1]
            ], dtype=np.float32)
        if ver_flag:
            flip = np.array([
                [1, 0, 0],
                [0, -1, image_width],
                [0, 0, 1]
            ], dtype=np.float32)
        self.update_transform_matrix(view_type, flip)

    def get_window_by_viewer_type(self, view_type):
        return self.view_info[view_type].window

    def resize(self, view_type, new_size):
        current_png_size = self.view_info[view_type].png_size
        scale_h = new_size[0] / current_png_size[0]
        scale_w = new_size[1] / current_png_size[1]
        scale = min(scale_w, scale_h)
        if scale < 1:
            scale = max(scale_w, scale_h)
        self.set_canvas_size(view_type, new_size)
        viewport_transform = self.view_info[view_type].viewport_transform
        zoom_matrix = np.array([
            [scale, 0, 0],
            [0, scale, 0],
            [0, 0, 1]
        ], dtype=np.float32)
        viewport_transform.scale = scale
        self.update_transform_matrix(view_type, zoom_matrix)

    def reset_mpr_slice(self):
        origin_shape = self.slice_position.origin_shape
        reset_origin_position = tuple(round(origin_shape[i] // 2) for i in range(3))
        self.update_origin_slice_position(reset_origin_position)

    def voxel_to_physical_coordinate(self, voxel_point):
        x = (voxel_point[1], voxel_point[2], voxel_point[0])
        physical_point = self.coord_transfer.origin_voxel_to_physical(x)
        return physical_point

    def update_mip_range(self):
        half_ax_dis = self.mip_data.mip_val[EnumViewType.Ax.value] * 0.5
        half_sag_dis = self.mip_data.mip_val[EnumViewType.Sag.value] * 0.5
        half_cor_dis = self.mip_data.mip_val[EnumViewType.Cor.value] * 0.5
        current_norm_voxel_coord = self.coord_transfer.origin_voxel_to_norm_voxel_vector3(
            self.slice_position.origin_position)
        norm_shape = self.slice_position.norm_shape
        ax_mip_norm_range = (
            max(current_norm_voxel_coord[0] - half_ax_dis, 0),
            min(current_norm_voxel_coord[0] + half_ax_dis, norm_shape[0] - 1)
        )
        sag_mip_norm_range = (
            max(current_norm_voxel_coord[1] - half_sag_dis, 0),
            min(current_norm_voxel_coord[1] + half_sag_dis, norm_shape[1] - 1)
        )
        cor_mip_norm_range = (
            max(current_norm_voxel_coord[2] - half_cor_dis, 0),
            min(current_norm_voxel_coord[2] + half_cor_dis, norm_shape[2] - 1)
        )

        self.mip_data.set_mip_range_in_norm_voxel({
            EnumViewType.Ax.value: ax_mip_norm_range,
            EnumViewType.Sag.value: sag_mip_norm_range,
            EnumViewType.Cor.value: cor_mip_norm_range,
        })

    def set_mip(self, useMIP, mip_mode, mip_val):
        ax_dis = mip_val[EnumViewType.Ax.value]
        sag_dis = mip_val[EnumViewType.Sag.value]
        cor_dis = mip_val[EnumViewType.Cor.value]
        self.mip_data.set_all(useMIP, mip_mode, mip_val)
        self.mip_data.set_mip_val({
            EnumViewType.Ax.value: ax_dis,
            EnumViewType.Sag.value: sag_dis,
            EnumViewType.Cor.value: cor_dis,
        })
        self.update_mip_range()

    def get_mip_info(self, view_type):
        if self.mip_data is None or self.mip_data.use_mip is False:
            return None
        ax_scale = self.view_info[EnumViewType.Ax.value].viewport_transform.scale
        sag_scale = self.view_info[EnumViewType.Sag.value].viewport_transform.scale
        cor_scale = self.view_info[EnumViewType.Cor.value].viewport_transform.scale
        match view_type:
            case EnumViewType.Ax.value:
                sag_dis = self.mip_data.mip_val[EnumViewType.Sag.value]
                cor_dis = self.mip_data.mip_val[EnumViewType.Cor.value]

                # ax 视图中代表 sag 和cor 的线
                sag_screen_dis = sag_dis * ax_scale
                cor_screen_dis = cor_dis * ax_scale

                return {
                    "delta_x": round(sag_screen_dis / 2),
                    "delta_y": round(cor_screen_dis / 2)
                }
            case EnumViewType.Sag.value:
                ax_dis = self.mip_data.mip_val[EnumViewType.Ax.value]
                cor_dis = self.mip_data.mip_val[EnumViewType.Cor.value]
                # sag 视图中 代表 ax 和 cor的线。
                ax_screen_dis = ax_dis * sag_scale
                cor_screen_dis = cor_dis * sag_scale
                return {
                    "delta_y": round(ax_screen_dis / 2),
                    "delta_x": round(cor_screen_dis / 2),
                }
            case EnumViewType.Cor.value:
                ax_dis = self.mip_data.mip_val[EnumViewType.Ax.value]
                sag_dis = self.mip_data.mip_val[EnumViewType.Sag.value]
                sag_screen_dis = sag_dis * cor_scale
                ax_screen_dis = ax_dis * cor_scale
                return {
                    "delta_x": round(sag_screen_dis / 2),
                    "delta_y": round(ax_screen_dis / 2)
                }

    def toggle_next(self, view_type, is_4d):
        if is_4d is True:
            self.phase_data.set_next()
        else:
            self.set_next_slice(view_type)
